﻿/*
 * Copyright (c) http://DesignBased.NET 
 * Author: http://t.163.com/Jeek 
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the 
 * Software  * without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the 
 * Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
 * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR
 * A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 
 * 
 */

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using T163.Open.Api.WP7.Model;
using System.Windows;

namespace T163.Open.Api.WP7
{
    internal class OAuthRequest
    {
        private List<Parameter> parameterList = new List<Parameter>();

        private OAuthRequest(Consumer consumer, AccessToken accessToken)
        {
            Parameters = new ReadOnlyCollection<Parameter>(parameterList);

            this.Consumer = consumer;
            this.AccessToken = accessToken;
        }

        /// <summary>
        /// 创建一个 OAuthRequest 实例并设置 Consumer 和 AccessToken  
        /// </summary>
        /// <param name="consumer">Consumer</param>
        /// <param name="accessToken">AccessToken</param>
        /// <returns>OAuthRequest 实例</returns>
        internal static OAuthRequest Create(Consumer consumer, AccessToken accessToken)
        {
            if (consumer == null || accessToken == null)
            {
                throw new ArgumentException("Consumer 和 AccessToken 不能为空。");
            }

            return new OAuthRequest(consumer, accessToken);
        }

        /// <summary>
        /// Consumer
        /// </summary>
        internal Consumer Consumer
        {
            get;
            private set;
        }

        /// <summary>
        /// Access Token.
        /// </summary>
        internal AccessToken AccessToken
        {
            get;
            private set;
        }

        /// <summary>
        /// 请求网址
        /// </summary>
        internal string RequestUrl
        {
            get;
            set;
        }

        [DefaultValue("itdaniu.com")]
        internal string Realm
        {
            get;
            set;
        }

        /// <summary>
        /// Http 请求的方法类型
        /// </summary>
        internal HttpMethodType MethodType
        {
            get;
            set;
        }

        /// <summary>
        /// 参数的只读列表
        /// </summary>
        internal ReadOnlyCollection<Parameter> Parameters
        {
            get;
            set;
        }

        internal List<Parameter> ParameterList
        {
            get
            {
                return parameterList;
            }
            set
            {
                parameterList = value;
            }
        }

        /// <summary>
        /// OAuth Header 字符串
        /// </summary>
        internal string AuthorizationHeader
        {
            get;
            set;
        }

        /// <summary>
        /// 复制一个当前实例
        /// </summary>
        /// <returns></returns>
        internal OAuthRequest Copy()
        {
            OAuthRequest newReq = new OAuthRequest(Consumer, AccessToken);
            newReq.RequestUrl = RequestUrl;
            newReq.MethodType = MethodType;
            newReq.Realm = Realm;

            foreach (Parameter p in Parameters)
            {
                newReq.ParameterList.Add(p);
            }

            newReq.AuthorizationHeader = AuthorizationHeader;
            return newReq;
        }
    }

    /// <summary>
    /// 扩展类
    /// </summary>
    internal static class OAuthRequestExtension
    {
        /// <summary>
        /// 设置别名
        /// </summary>
        /// <param name="req">OAuthRequest 实例</param>
        /// <param name="realm">别名</param>
        /// <returns>OAuthRequest 实例</returns>
        internal static OAuthRequest SetRealm(this OAuthRequest req, string realm)
        {
            OAuthRequest r = req as OAuthRequest;
            OAuthRequest newReq = r.Copy();
            newReq.Realm = realm;
            return newReq;
        }

        /// <summary>
        /// 设置请求的地址
        /// </summary>
        /// <param name="req">OAuthRequest 实例</param>
        /// <param name="url">请求的地址</param>
        /// <returns>OAuthRequest 实例</returns>
        internal static OAuthRequest SetUrl(this OAuthRequest req, string url)
        {
            OAuthRequest r = req as OAuthRequest;
            OAuthRequest newReq = r.Copy();
            newReq.RequestUrl = url;
            return newReq;
        }

        /// <summary>
        /// 设置请求的Http 方法类型
        /// </summary>
        /// <param name="req">OAuthRequest 实例</param>
        /// <param name="type">Http 方法类型</param>
        /// <returns>OAuthRequest 实例</returns>
        internal static OAuthRequest SetHttpMethod(this OAuthRequest req, HttpMethodType type)
        {
            OAuthRequest r = req as OAuthRequest;
            OAuthRequest newReq = r.Copy();
            newReq.MethodType = type;
            return newReq;
        }

        /// <summary>
        /// 添加参数
        /// </summary>
        /// <param name="req">OAuthRequest</param>
        /// <param name="args">Parameter</param>
        /// <returns>OAuthRequest 实例</returns>
        internal static OAuthRequest AddParameter(this OAuthRequest req, params Parameter[] args)
        {
            OAuthRequest r = req as OAuthRequest;
            OAuthRequest newReq = r.Copy();

            foreach (Parameter p in args)
            {
                if (!string.IsNullOrEmpty(p.Value))
                    newReq.ParameterList.Add(p);
            }

            return newReq;
        }

        /// <summary>
        /// 清除参数
        /// </summary>
        /// <param name="req">OAuthRequest</param>
        /// <returns>OAuthRequest 实例</returns>
        internal static OAuthRequest ClearParameter(this OAuthRequest req)
        {
            OAuthRequest r = req as OAuthRequest;
            OAuthRequest newReq = r.Copy();
            newReq.ParameterList.Clear();

            return newReq;
        }

        /// <summary>
        /// 移除指定键值的参数
        /// </summary>
        /// <param name="req">OAuthRequest</param>
        /// <param name="parameterKey">Parameter 键值</param>
        /// <returns>OAuthRequest</returns>
        internal static OAuthRequest RemoveParameter(this OAuthRequest req, string parameterKey)
        {
            OAuthRequest r = req as OAuthRequest;
            OAuthRequest newReq = r.Copy();
            var targets = newReq.ParameterList.Where((p) =>
            {
                return parameterKey == p.Key;
            });

            foreach (Parameter p in targets)
            {
                newReq.ParameterList.Remove(p);
            }
            return newReq;
        }

        /// <summary>
        /// 提交请求
        /// </summary>
        /// <param name="req">OAuthRequest</param>
        /// <exception cref="System.ArgumentException">ArgumentException</exception>
        /// <returns>OAuthRequest 实例</returns>
        internal static OAuthRequest CommitRequest(this OAuthRequest req)
        {
            OAuthRequest r = req as OAuthRequest;

            if (string.IsNullOrEmpty(r.RequestUrl))
            {
                throw new ArgumentException("未设置提交请求的地址");
            }

            OAuthRequest newReq = r.Copy();

            newReq.AuthorizationHeader = OAuthUtility.GenerateAuthorizationHeader(
                r.Realm,
                r.RequestUrl,
                null,
                r.MethodType,
                r.Consumer,
                r.AccessToken,
                null,
                r.Parameters);

            return newReq;
        }

        internal static void GetResponse(this OAuthRequest req, Action<OAuthResponse> action, Action<Exception> fail)
        {
            OAuthRequest oauthReq = req as OAuthRequest;
            HttpWebRequest httpReq;

            if (oauthReq.MethodType == HttpMethodType.Get)
            {
                string newUrl = oauthReq.RequestUrl;
                if (oauthReq.Parameters.Count != 0)
                {
                    newUrl += "?";
                }
                newUrl = newUrl + string.Join("&", oauthReq.Parameters.Select((p) => { return p.Key + "=" + p.Value.UrlEncode(); }).ToArray());
                httpReq = WebRequest.Create(newUrl) as HttpWebRequest;
                httpReq.Method = "GET";
            }
            else
            {
                httpReq = WebRequest.Create(oauthReq.RequestUrl) as HttpWebRequest;
                httpReq.Method = "POST";
                httpReq.ContentType = "application/x-www-form-urlencoded";
            }

            httpReq.Headers["Authorization"] = oauthReq.AuthorizationHeader;
            
            string para = string.Join("&", oauthReq.Parameters.Select((p) => { return p.Key + "=" + p.Value.UrlEncode(); }).ToArray());
            byte[] paraBytes = Encoding.UTF8.GetBytes(para);

            if (httpReq.Method == "POST")
            {
                try
                {
                    httpReq.BeginGetRequestStream(p =>
                    {
                        try
                        {
                            HttpWebRequest request = (HttpWebRequest)(((object[])p.AsyncState)[0]);
                            byte[] postBytes = (byte[])(((object[])p.AsyncState)[1]);
                            using(Stream postStream = request.EndGetRequestStream(p))
                                postStream.Write(postBytes, 0, postBytes.Length);

                            GetResponse(request, action, fail);
                        }
                        catch(Exception ex)
                        {
                            HandleErrors(ex, fail);
                        }
                    }, new object[] { httpReq, paraBytes });
                }
                catch(Exception ex)
                {
                    HandleErrors(ex, fail);
                }
            }
            else
            {
                GetResponse(httpReq, action, fail);
            }
        }

        private static void GetResponse(HttpWebRequest request, Action<OAuthResponse> action, Action<Exception> fail)
        {
            try
            {
                request.BeginGetResponse(o =>
                {
                    try
                    {
                        HttpWebRequest getReq = (HttpWebRequest)o.AsyncState;
                        HttpWebResponse response = (HttpWebResponse)getReq.EndGetResponse(o);

                        OAuthResponse oauthResponse = new OAuthResponse(response);
                        action(oauthResponse);

                        response.Close();
                    }
                    catch(Exception ex)
                    {
                        HandleErrors(ex, fail);
                    }
                }, request);
            }
            catch(Exception ex)
            {
                HandleErrors(ex, fail);
            }
        }

        private static void HandleErrors(Exception ex, Action<Exception> fail)
        {
            WebException we = ex as WebException;

            if (we != null && we.Status == WebExceptionStatus.RequestCanceled)
                return;

            if (fail != null)
                Deployment.Current.Dispatcher.BeginInvoke(() => fail(ex));
            else
                Deployment.Current.Dispatcher.BeginInvoke(() =>
                    {
                        MessageBox.Show(ex.Message);
                    });
        }
    }
}
